<?php
/**
 * Map
 *
 * @category	General
 * @package		dayscript
 * @subpackage
 * @author		Nelson Daza <ndaza@dayscript.com>
 * @copyright	2009 Dayscript Ltda.
 * @license
 * @version		1.0
 * @version		$Revision: 0 $
 * @filesource
 * @link		http://www.dayscript.com
 * @link		{docsLink}
 * @uses
 * @since		1.0
 * @modifiedby	$LastChangedBy: Nelson Daza $
 * @modified	$Date: 2009-08-13 $
 */

namespace dayscript;

/**
 * An object that maps keys to values.
 *
 * A map cannot contain duplicate keys, each key can map only one value.
 *
 * @category	General
 * @package		dayscript
 * @subpackage
 * @author		Nelson Daza <ndaza@dayscript.com>
 * @copyright	2009 Dayscript Ltda.
 * @license
 * @version		1.0
 * @link		http://www.dayscript.com
 * @link		{docsLink}
 * @uses		\Countable, \Iterator, \Serializable, \ArrayAccess
 * @since		1.0
 * @modifiedby	$LastChangedBy: Nelson Daza $
 * @modified	$Date: 2009-08-13 $
 */
class Map implements \Countable, \Iterator, \Serializable, \ArrayAccess	{
	/**
	 * Total elements in the map
	 * @var int $length
	 * @access private
	 */
	private $length;
	/**
	 * Actual cursor position
	 * @var int position
	 * @access private
	 */
	private $position;
	/**
	 * Elements in the map
	 *
	 * @var array $storage
	 * @access private
	 */
	private $storage;
	/**
	 * Map contructor
	 * @param array|\dayscript\Map $data
	 * @return Map
	 */
	public function __construct( $data = null )	{
		$this->clear( );
		$this->putAll( $data );
	}
	/**
	 * Returns actual count of elements
	 * @return int $length
	 * @see Countable
	 */
	public function count( )	{
		if ( $this->length < 0 )
			$this->length = \count( $this->storage );
		return $this->length;
	}
	/**
	 * Sets actual maps position to cero
	 * @return int $length
	 * @see \Iterator
	 */
	public function rewind( )	{
		$this->position = 0;
		\reset( $this->storage );
	}
	public function current( )	{
		return \current( $this->storage );
	}
	public function key( )	{
		return \key( $this->storage );
	}
	public function next( )	{
		$this->position ++;
		\next( $this->storage );
	}
	public function valid( )	{
		return ( $this->position < $this->count( ) );
	}
	/**
	 * @see \Serializable
	 */
	public function serialize( )	{
		return \serialize( $this->storage );
	}
	public function unserialize( $data ) {
		$this->clear( );
		$this->storage = \unserialize( $data );
		$this->length = -1;
	}
	/**
	 * @see \ArrayAccess
	 */
	public function offsetGet( $offset )	{
		return ( \array_key_exists( $offset, $this->storage ) ? $this->storage[$offset] : null );
	}
	public function offsetSet( $offset, $value )	{
		$this->storage[$offset] = $value;
		$this->length = -1;
	}
	public function offsetExists( $offset )	{
		return \array_key_exists( $offset, $this->storage );
	}
	public function offsetUnset( $offset )	{
		unset( $this->storage[$offset] );
		$this->length = -1;
	}
	/**
	 * Magic method __isset for use when calling isset() or empty()
	 */
	public function __isset( $offset )	{
		return \array_key_exists( $offset, $this->storage );
	}
	/**
	 * Magic method __isset for use when calling isset() or empty()
	 */
	public function __unset( $offset )	{
		unset( $this->storage[$offset] );
		$this->length = -1;
	}
	/**
	 * Resets all Map data.
	 */
	public function clear( )	{
		$this->length = -1;
		$this->position = 0;
		$this->storage = array( );
	}
	/**
	 * Whether or not an offset exists.
	 * @param mixed $offset
	 * @return boolean
	 */
	public function containsKey( $offset )	{
		return $this->offsetExists( $offset );
	}
	/**
	 * Whether or not a value exists.
	 * @param mixed $value
	 * @param boolean $strict
	 * @return boolean
	 */
	public function containsValue( $value, $strict = false )	{
		return \in_array( $value, $this->storage, $strict );
	}
	/**
	 * Returns the value to which this map maps the specified key.
	 * @param mixed $offset
	 * @param mixed $default
	 * @return mixed|$default $value The value to which this map maps the specified key, or $default if the map contains no mapping for this key.
	 */
	public function get( $offset, $default = null )	{
		$result = $this->offsetGet( $offset, $default );
		return ( $result === null ? $default : $result );
	}
	/**
	 * Returns true if this map contains no key-value mappings.
	 * @return boolean
	 */
	public function isEmpty( )	{
		return ( $this->count( ) <= 0 );
	}
	/**
	 * Associates the specified value with the specified key in this map.
	 * @param mixed $offset
	 * @param mixed $value
	 * @return mixed|null $actual Previous value associated with specified key, or null if there was no mapping for key
	 */
	public function put( $offset, $value )	{
		$actual = $this[$offset];
		$this->offsetSet( $offset, $value );
		return $actual;
	}
	/**
	 * Removes the mapping for this key from this map if it is present.
	 * @param mixed $offset key whose mapping is to be removed from the map.
	 * @return mixed|null $actual Value to which the map previously associated the key, or null if the map contained no mapping for this key.
	 */
	public function remove( $offset )	{
		$actual = $this[$offset];
		unset( $this[$offset] );
		return $actual;
	}
	/**
	 * Returns the number of key-value mappings in this map.
	 * @return int $length The number of key-value mappings in this map.
	 */
	public function size( )	{
		return $this->count( );
	}
	/**
	 * Returns an array of the keys contained in this map.
	 * @return array $keys
	 */
	public function keys( )	{
		return \array_keys( $this->storage );
	}
	/**
	 * Returns an array of the values contained in this map.
	 * @return array $values
	 */
	public function values( )	{
		return \array_values( $this->storage );
	}
	/**
	 * Copy all information into current Map
	 * @param array|Map $data
	 */
	public function putAll( $data )	{
		if( $data != null )	{
			foreach ( $data as $key => $value )	{
				if( $value instanceOf Map )
					$this->storage[$key] = new Map( $value );
				else
					$this->storage[$key] = $value;
			}
			$this->length = -1;
			// Performance
			//foreach ( $data as $key => $value )
				//$this->offsetSet( $key, ( \is_array( $value ) || $value instanceOf \dayscript\Map ? new \dayscript\Map( $value ) : $value ) );
		}
	}
	/**
	 * Returns a copy of all information into an array
	 * @return array $map
	 */
	public function toArray( )	{
		$map = array ( );
		foreach( $this->storage as $key => $value )	{
			if( $value instanceOf \dayscript\Map )
				$map[$key] = $value->toArray( );
			else
				$map[$key] = $value;
		}
		return $map;
	}
}